package com.topchain.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Validator;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.topchain.common.ResponseCodeEnum;
import com.topchain.common.ResultMessage;
import com.topchain.constant.CommonConstant;
import com.topchain.entity.Licence;
import com.topchain.entity.RootCert;
import com.topchain.entity.UserCert;
import com.topchain.entity.cert.GenCert;
import com.topchain.entity.cert.RSA;
import com.topchain.exception.SystemException;
import com.topchain.licence.LicenceContent;
import com.topchain.service.LicenceService;
import com.topchain.service.RootCertService;
import com.topchain.service.UserCertService;
import com.topchain.utils.CommUtil;
import com.topchain.utils.CryptoUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.io.*;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECParameterSpec;
import java.util.Base64;
import java.util.Date;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@Api(tags = {"licence"})
@RestController
@RequestMapping(value = "/licence")
public class LicenceController {

    private final LicenceService licenceService;
    private final UserCertService userCertService;
    private final RootCertService rootCertService;

    public LicenceController(LicenceService licenceService,
                             UserCertService userCertService,
                             RootCertService rootCertService) {
        this.licenceService = licenceService;
        this.userCertService = userCertService;
        this.rootCertService = rootCertService;
    }

    @ApiOperation(value = "下载licence")
    @GetMapping(value = "/downloadLicence")
    public ResponseEntity<InputStreamResource> downloadLicence(@ApiParam(value = "licence文件id")
                                                               @RequestParam("licenceId") Integer licenceId) {
        try {
            Licence licence = licenceService.getById(licenceId);
            File tempLicenceFile = getLicenceTempFile(licence.getLicence());
            FileInputStream fileInputStream = new FileInputStream(tempLicenceFile);

            String fileName = URLEncoder.encode("licence.lic", "UTF-8");

            InputStreamResource inputStreamResource = new InputStreamResource(fileInputStream);

            tempLicenceFile.delete();
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName)
                    .contentLength(tempLicenceFile.length())
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .body(inputStreamResource);
        }catch (Exception e){
            throw new SystemException(ResponseCodeEnum.DOWNLOAD_LICENCE_EXCEPTION);
        }
        
    }

    @Deprecated
    @ApiOperation(value = "下载licence",hidden = true)
    @GetMapping(value = "/downloadLicenceOld")
    public ResponseEntity<InputStreamResource> downloadLicenceOld(@ApiParam(value = "licence文件id")
                                                               @RequestParam("licenceId") Integer licenceId) {
        // 1. 查询Licence对象，获取其中的licence信息，创建一个临时licence文件
        Licence licence = licenceService.getById(licenceId);
        String licenceUser = licence.getUser();
        if (licenceUser.length() < 3) {
            licenceUser = licenceUser + "_" + System.currentTimeMillis();
        }
        String licenceContent = licence.getLicence();
        File tempLicenceFile = getLicenceTempFile(licenceContent);

        // 2. 查询根证书对象，获取其中的证书内容，创建一个临时根证书文件
        Integer userCertId = licence.getUserCertId();
        UserCert userCert = userCertService.getById(userCertId);
        RootCert rootCert = rootCertService.getById(userCert.getRootCertId());
        File tempRootCertFile = getCertTempFile(rootCert.getCertBlob(), "StandardCaRootCert");

        // 3. 查询用户证书对象，获取其中的证书内容，创建一个临时用户证书文件
        File tempUserCertFile = getCertTempFile(userCert.getCertBlob(), "StandardUserCert");

        // 将上述三个临时文件打包成zip文件并下载
        File zipFile;
        try {
            zipFile = File.createTempFile(licenceUser, ".zip");
            // 创建ZipOutputStream对象
            ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile.toPath()));

            // 添加licence文件到压缩包中
            String licenceFileName = "licence.lic";
            addFileToZip(zipOut, tempLicenceFile, licenceFileName);
            tempLicenceFile.delete();

            // 添加根证书文件到压缩包中
            String rootCertName = "StandardCaRootCert.pem";
            addFileToZip(zipOut, tempRootCertFile, rootCertName);
            tempRootCertFile.delete();

            // 添加用户证书到压缩包中
            String userCertName = "StandardUserCert.pem";
            addFileToZip(zipOut, tempUserCertFile, userCertName);
            tempUserCertFile.delete();

            zipOut.close();
            InputStreamResource resource = new InputStreamResource(Files.newInputStream(zipFile.toPath()));
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            String fileName = "licence.zip";
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + new String(fileName.getBytes(StandardCharsets.UTF_8), "ISO8859-1"));
            return new ResponseEntity<>(resource, headers, HttpStatus.OK);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @ApiOperation(value = "licence续期")
    @PostMapping(value = "/renewalLicence")
    public ResultMessage renewalLicence(@ApiParam(value = "licence文件id")
                                        @RequestParam(value = "licenceId") Integer licenceId,
                                        @ApiParam(value = "更新后的截至时间")
                                        @RequestParam(value = "days") Integer days) {
        Licence licenceFromDB = licenceService.getById(licenceId);
        if (Objects.isNull(licenceFromDB)) {
            return ResultMessage.errorResult(ResponseCodeEnum.SYSTEM_EXCEPTION.getCode(), "licence不存在");
        }
        if (Objects.isNull(days) || days <= 0) {
            throw new SystemException(ResponseCodeEnum.RENEWAL_EXCEPTION);
        }
        long notAfterTimeNew = Long.parseLong(licenceFromDB.getNotAfterTime()) + (days * 86400000L);

        // 更新用户证书(licence续期时，需要重新生成用户证书)
        updateUserCertById(licenceFromDB.getUserCertId(), notAfterTimeNew);

        UserCert userCertFromDB = userCertService.getById(licenceFromDB.getUserCertId());
        licenceFromDB.setNotAfterTime(String.valueOf(notAfterTimeNew));

        LicenceContent licenceContent = new LicenceContent();
        licenceContent.setUser(licenceFromDB.getUser());
        licenceContent.setAllowIp(licenceFromDB.getAllowIp());
        licenceContent.setMac(licenceFromDB.getMac());
        licenceContent.setNotBeforeTime(Long.parseLong(licenceFromDB.getNotBeforeTime()));
        licenceContent.setNotAfterTime(notAfterTimeNew);
        generateNewLicence(licenceContent, userCertFromDB, licenceFromDB);
        return ResultMessage.success("licence续期成功", true);
    }

    private void updateUserCertById(Integer userCertId, long notAfterTimeNew) {
        try {
            UserCert userCertFromDB = userCertService.getById(userCertId);
            RSA rsa = new RSA();
            KeyPair keyPair = rsa.genKeyPair(2048);
            LambdaQueryWrapper<RootCert> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(RootCert::getCertName, CommonConstant.ROOT_CERT_NAME);
            RootCert rootCert = rootCertService.getOne(queryWrapper);
            KeyPair rootKeyPair = CryptoUtil.parseKeyPairFromPemStr(rootCert.getPrivateKey(), "");

            String rootCertContent = rootCert.getCertBlob();
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
            X509Certificate caRootCert = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(rootCertContent.getBytes()));

            // 用户证书的DN生成规则：CN=用户姓名,C=CN,ST=guangdong,L=shenzhen,O=R&D,OU=IT
            String userDN = userCertFromDB.getSubjectDn();
            X509Certificate certificate = new GenCert().genCertWithCaSign_V2(caRootCert, rootKeyPair.getPrivate(), keyPair,
                    CommonConstant.SIGN_ALGORITHM, userDN, DateUtil.parseDateTime(userCertFromDB.getNotBefore()).getTime(), notAfterTimeNew, "www.rsa-user.com", "user@qq.com");
            userCertFromDB.setNotAfter(certificate.getNotAfter().toLocaleString());
            userCertFromDB.setSerialNumber(getCertificateSerialNumber(certificate.getSerialNumber()));
            userCertFromDB.setPrivateKey(CryptoUtil.convertBase64Pem(keyPair.getPrivate()));
            userCertFromDB.setPublicKey(CryptoUtil.convertBase64Pem(keyPair.getPublic()));
            userCertFromDB.setCertBlob(CryptoUtil.convertBase64Pem(certificate));
            userCertFromDB.setSignature(Base64.getEncoder().encodeToString(certificate.getSignature()));
            userCertService.updateById(userCertFromDB);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void generateNewLicence(LicenceContent licenceContent, UserCert userCert, Licence licence) {
        StringBuilder sb = new StringBuilder();
        sb.append(licenceContent.getUser())
                .append(licenceContent.getAllowIp())
                .append(licenceContent.getMac())
                .append(licenceContent.getNotBeforeTime())
                .append(licenceContent.getNotAfterTime());

        try {
            RSA rsa = new RSA();
            KeyPair userKeyPair = CryptoUtil.parseKeyPairFromPemStr(userCert.getPrivateKey(), "");
            byte[] sign = rsa.sign(sb.toString().getBytes(), userKeyPair.getPrivate(), CommonConstant.KEY_ALGORITHM);
            // 设置签名值
            licenceContent.setSign(new String(sign));
            // 获取根证书内容
            Integer rootCertId = userCert.getRootCertId();
            String rootCertContent = rootCertService.getById(rootCertId).getCertBlob();
            licenceContent.setRootCertContent(rootCertContent);
            licenceContent.setUserCertContent(userCert.getCertBlob());
            
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            try (ObjectOutputStream out = new ObjectOutputStream(bos)) {
                out.writeObject(licenceContent);
                licence.setLicence(Base64.getEncoder().encodeToString(bos.toByteArray()));
            }
            licence.setVersion(licence.getVersion() + 1);
            String licenceDigest = CommUtil.getDigestWithSHA1(bos.toByteArray());
            licence.setDigest(licenceDigest);
            licenceService.updateById(licence);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @ApiOperation(value = "licence列表分页查询")
    @GetMapping(value = "/licencePage")
    public ResultMessage<Page<Licence>> licencePage(@ApiParam("用户名") @RequestParam(value = "user", required = false) String user,
                                                    @ApiParam("当前页") @RequestParam(value = "pageNo", required = false, defaultValue = "1") Integer pageNo,
                                                    @ApiParam("每页显示数量") @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
        LambdaQueryWrapper<Licence> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.hasText(user), Licence::getUser, user);
        Page<Licence> licencePage = new Page<>(pageNo, pageSize);
        licenceService.page(licencePage, queryWrapper);
        for (Licence record : licencePage.getRecords()) {
            record.setNotBeforeTime(DateUtil.formatDateTime(new Date(Long.parseLong(record.getNotBeforeTime()))));
            record.setNotAfterTime(DateUtil.formatDateTime(new Date(Long.parseLong(record.getNotAfterTime()))));
        }
        return ResultMessage.success(licencePage);
    }

    @ApiOperation(value = "licence详情")
    @GetMapping(value = "/getLicenceInfoById")
    public ResultMessage<Licence> getLicenceInfoById(@ApiParam(value = "licence文件id")
                                                     @RequestParam(value = "licenceId") Integer licenceId) {
        Licence licence = licenceService.getById(licenceId);
        if (!Objects.isNull(licence)) {
            licence.setNotBeforeTime(DateUtil.formatDateTime(new Date(Long.parseLong(licence.getNotBeforeTime()))));
            licence.setNotAfterTime(DateUtil.formatDateTime(new Date(Long.parseLong(licence.getNotAfterTime()))));
            return ResultMessage.success(licence);
        }
        return ResultMessage.errorResult(ResponseCodeEnum.SYSTEM_EXCEPTION.getCode(), "licence不存在");
    }

    @Transactional(rollbackFor = {Exception.class})
    @ApiOperation(value = "对用户信息进行签名并生成和用户(使用者)绑定的licence文件")
    @PostMapping(value = "/generateLicence")
    public ResultMessage<Void> generateLicence(@RequestBody LicenceContent licenceContent) throws Exception {
        verifyParameter(licenceContent);
        log.info("----------------------------------------------------------");
        log.info("-----------------------生成标准用户证书----------------------");
        log.info("----------------------------------------------------------");

        // String certName = licenceContent.getCertName();
        String certName = licenceContent.getUser(); // 用户姓名 => 用户证书标识名
        // LambdaQueryWrapper<UserCert> userCertLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // userCertLambdaQueryWrapper.eq(UserCert::getCertName, certName);
        // UserCert userCertFromDB = userCertService.getOne(userCertLambdaQueryWrapper);
        // if (!Objects.isNull(userCertFromDB))
        // return ResultMessage.errorResult(ResponseCodeEnum.SYSTEM_EXCEPTION.getCode(), "用户证书标识名已存在");
        certName = certName + CommUtil.getRandomStr();

        RSA rsa = new RSA();
        KeyPair keyPair = rsa.genKeyPair(2048);
        String rootCertName = licenceContent.getRootCertName();
        if (StringUtils.isEmpty(rootCertName)) rootCertName = CommonConstant.ROOT_CERT_NAME;
        LambdaQueryWrapper<RootCert> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(RootCert::getCertName, rootCertName);
        RootCert rootCert = rootCertService.getOne(queryWrapper);
        KeyPair rootKeyPair = CryptoUtil.parseKeyPairFromPemStr(rootCert.getPrivateKey(), "");

        String rootCertContent = rootCert.getCertBlob();
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
        X509Certificate caRootCert = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(rootCertContent.getBytes()));

        // 用户证书的DN生成规则：CN=用户姓名,C=CN,ST=guangdong,L=shenzhen,O=R&D,OU=IT
        String userDN = "CN=" + certName + ",C=CN,ST=guangdong,L=shenzhen,O=R&D,OU=IT";
        X509Certificate certificate = new GenCert().genCertWithCaSign_V2(caRootCert, rootKeyPair.getPrivate(), keyPair,
                CommonConstant.SIGN_ALGORITHM, userDN, licenceContent.getNotBeforeTime(), licenceContent.getNotAfterTime(), "www.rsa-user.com", "user@qq.com");

        UserCert userCert = new UserCert();
        // 自定义的用户证书标识名
        userCert.setCertName(certName);
        userCert.setVersion(String.valueOf(certificate.getVersion()));
        userCert.setSubjectDn(certificate.getSubjectDN().toString());
        userCert.setIssuerDn(certificate.getIssuerDN().toString());
        userCert.setNotBefore(certificate.getNotBefore().toLocaleString());
        userCert.setNotAfter(certificate.getNotAfter().toLocaleString());
        userCert.setKeyAlg(getCertAlgorithm(certificate));
        userCert.setKeyLength(getKeyLength(certificate));
        userCert.setSignAlg(certificate.getSigAlgName());
        userCert.setSerialNumber(getCertificateSerialNumber(certificate.getSerialNumber()));
        userCert.setPrivateKey(CryptoUtil.convertBase64Pem(keyPair.getPrivate()));
        userCert.setPublicKey(CryptoUtil.convertBase64Pem(keyPair.getPublic()));
        userCert.setCertBlob(CryptoUtil.convertBase64Pem(certificate));
        userCert.setSignature(Base64.getEncoder().encodeToString(certificate.getSignature()));
        userCert.setRootCertId(rootCert.getId());
        userCertService.save(userCert);
        log.info("由根证书签发用户证书成功");


        log.info("----------------------------------------------------------");
        log.info("-----------------通过用户证书签发licence文件-----------------");
        log.info("----------------------------------------------------------");

        StringBuilder sb = new StringBuilder();
        sb.append(licenceContent.getUser())
                .append(licenceContent.getAllowIp())
                .append(licenceContent.getMac())
                .append(licenceContent.getNotBeforeTime())
                .append(licenceContent.getNotAfterTime());

        // 使用私钥对licence信息进行签名 (被签名的信息示例：shuguangyun192.168.5.3138-CA-84-46-C2-1717289216000001744646399000)
        byte[] sign = rsa.sign(sb.toString().getBytes(), keyPair.getPrivate(), CommonConstant.KEY_ALGORITHM);
        // 设置签名值
        licenceContent.setSign(new String(sign));
        licenceContent.setRootCertContent(rootCert.getCertBlob());
        licenceContent.setUserCertContent(userCert.getCertBlob());

        Licence licence = new Licence();
        licence.setUser(licenceContent.getUser());
        licence.setAllowIp(licenceContent.getAllowIp());
        licence.setMac(licenceContent.getMac());
        licence.setNotBeforeTime(String.valueOf(licenceContent.getNotBeforeTime()));
        licence.setNotAfterTime(String.valueOf(licenceContent.getNotAfterTime()));
        licence.setUserCertId(userCert.getId());

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (ObjectOutputStream out = new ObjectOutputStream(bos)) {
            out.writeObject(licenceContent);
            licence.setLicence(Base64.getEncoder().encodeToString(bos.toByteArray()));
        }
        licence.setVersion(1);
        String licenceDigest = CommUtil.getDigestWithSHA1(bos.toByteArray());
        licence.setDigest(licenceDigest);
        licenceService.save(licence);
        return ResultMessage.success("licence文件成功生成", true);
    }

    private void verifyParameter(LicenceContent licenceContent) {
        // 校验ip地址的合法性
        if (!verifyIP(licenceContent.getAllowIp())) {
            throw new SystemException(ResponseCodeEnum.IP_EXCEPTION);
        }
        // 校验Mac地址合法性
        if (!verifyMac(licenceContent.getMac())) {
            throw new SystemException(ResponseCodeEnum.MAC_EXCEPTION);
        }
        // 校验时间合法性
        if (!verifyTime(licenceContent.getNotBeforeTime(), licenceContent.getNotAfterTime())) {
            throw new SystemException(ResponseCodeEnum.START_TIME_EXCEPTION);
        }
    }

    private boolean verifyTime(long startTime, long endTime) {
        return endTime > startTime;
    }

    private boolean verifyMac(String mac) {
        return Validator.isMac(mac);
    }

    private boolean verifyIP(String allowIp) {
        return Validator.isIpv4(allowIp);
    }

    private void addFileToZip(ZipOutputStream zipOut, File file, String fileName) throws IOException {
        FileInputStream fis = new FileInputStream(file);
        ZipEntry zipEntry = new ZipEntry(fileName);
        zipOut.putNextEntry(zipEntry);

        byte[] bytes = new byte[1024];
        int length;
        while ((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        fis.close();
    }

    private File getCertTempFile(String certBlob, String prefix) {
        try {
            byte[] fileContent = certBlob.getBytes(StandardCharsets.UTF_8);
            File tempFile = File.createTempFile(prefix, ".pem");
            FileOutputStream fos = new FileOutputStream(tempFile);
            fos.write(fileContent);
            fos.close();
            return tempFile;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private File getLicenceTempFile(String str) {
        byte[] licenceBytes = Base64.getDecoder().decode(str);
        try {
            File tempFile = File.createTempFile("licence", "lic");
            ByteArrayInputStream bis = new ByteArrayInputStream(licenceBytes);
            ObjectInputStream ois = new ObjectInputStream(bis);
            Object object = ois.readObject();
            LicenceContent licenceContent = (LicenceContent) object;
            ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(tempFile.toPath()));
            oos.writeObject(licenceContent);
            oos.close();
            return tempFile;
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private static String getCertAlgorithm(X509Certificate cert) {
        PublicKey publicKey = cert.getPublicKey();
        String algorithm = publicKey.getAlgorithm();
        if ("EC".equals(algorithm)) {
            if (publicKey instanceof ECPublicKey) {
                BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
                ECParameterSpec ecParameterSpec = bcecPublicKey.getParams();
                ECNamedCurveSpec ecNamedCurveSpec = (ECNamedCurveSpec) ecParameterSpec;
                String curveSpecName = ecNamedCurveSpec.getName();
                if ("sm2p256v1".equals(curveSpecName)) {
                    return CommonConstant.SM2;
                }
            } else {
                return algorithm;
            }
        } else if (CommonConstant.RSA.equals(algorithm)) {
            return CommonConstant.RSA;
        } else {
            return algorithm;
        }
        return null;
    }

    private static int getKeyLength(X509Certificate cert) {
        int publicKeyLength = 0;
        PublicKey publicKey = cert.getPublicKey();
        if (publicKey instanceof RSAPublicKey) {
            publicKeyLength = ((RSAPublicKey) publicKey).getModulus().bitLength();
        } else if (publicKey instanceof ECPublicKey) {
            return CommonConstant.SM2_KEY_SIZE;
        }
        return publicKeyLength;
    }

    private static String getCertificateSerialNumber(BigInteger serialNumber) {
        String serialNumberHex = serialNumber.toString(16);
        if (serialNumberHex.length() % 2 == 1) {
            serialNumberHex = "0" + serialNumberHex;
        }
        return serialNumberHex;
    }
}
